{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 220,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# simple neural network of one complex valued neuron\n",
    "# extended to use a periodic activation function\n",
    "import numpy\n",
    "# pandas for reading csv files\n",
    "import pandas\n",
    "# sklearn for splitting data into test/train sets\n",
    "import sklearn.cross_validation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 221,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class neuralNetwork:\n",
    "    \n",
    "    def __init__(self, inputs, cats, periods):\n",
    "        # number of inputs\n",
    "        self.inputs = inputs\n",
    "        \n",
    "        # link weights matrix\n",
    "        self.w = numpy.random.normal(0.0, pow(1.0, -0.5), (self.inputs + 1))\n",
    "        self.w = numpy.array(self.w, ndmin=2, dtype='complex128')\n",
    "        self.w += 1j * numpy.random.normal(0.0, pow(1.0, -0.5), (self.inputs + 1))\n",
    "        \n",
    "        # testing overrride\n",
    "        #self.w = numpy.array([1.0 + 0.0j, 1.0 + 0.0j], ndmin=2, dtype='complex128')\n",
    "        \n",
    "        # number of output class categories\n",
    "        self.categories = cats\n",
    "        \n",
    "        # todo periodicity\n",
    "        self.periodicity = periods\n",
    "        \n",
    "        pass\n",
    "    \n",
    "    def z_to_class(self, z):\n",
    "        # first work out the angle, but shift angle from [-pi/2, +pi.2] to [0,2pi]\n",
    "        angle = numpy.mod(numpy.angle(z) + 2*numpy.pi, 2*numpy.pi)\n",
    "        # from angle to category\n",
    "        p = int(numpy.floor (self.categories * self.periodicity * angle / (2*numpy.pi)))\n",
    "        p = numpy.mod(p, self.categories)\n",
    "        return p\n",
    "\n",
    "    def class_to_angles(self, c):\n",
    "        # class to several angles due to periodicity, using bisector\n",
    "        angles = (c + 0.5 + (self.categories * numpy.arange(self.periodicity))) / (self.categories * self.periodicity) * 2 * numpy.pi\n",
    "        return angles\n",
    "    \n",
    "    def status(self):\n",
    "        print (\"w = \", self.w)\n",
    "        print (\"categories = \", self.categories)\n",
    "        print (\"periodicity = \", self.periodicity)\n",
    "        pass\n",
    "\n",
    "    def query(self, inputs_list):\n",
    "        # add bias input\n",
    "        inputs_list.append(1.0)\n",
    "        \n",
    "        # convert input to complex\n",
    "        inputs = numpy.array(inputs_list, ndmin=2, dtype='complex128').T\n",
    "        #print(\"inputs = \\n\", inputs)\n",
    "        \n",
    "        # combine inputs, weighted\n",
    "        z = numpy.dot(self.w, inputs)\n",
    "        #print(\"z = \", z)\n",
    "        \n",
    "        # map to output classes\n",
    "        o = self.z_to_class(z)\n",
    "        #print(\"output = \", o)\n",
    "        #print (\"\")\n",
    "        return o\n",
    "    \n",
    "    def train(self, inputs_list, target):\n",
    "        # add bias input\n",
    "        inputs_list.append(1.0)\n",
    "        \n",
    "        # convert inputs and outputs list to 2d array\n",
    "        inputs = numpy.array(inputs_list, ndmin=2, dtype='complex128').T\n",
    "\n",
    "        # combine inputs, weighted\n",
    "        z = numpy.dot(self.w, inputs)[0]\n",
    "        \n",
    "        # desired angle from trainging set\n",
    "        # first get all possible angles\n",
    "        desired_angles = self.class_to_angles(target)\n",
    "        \n",
    "        # potential errors errors\n",
    "        errors =  numpy.exp(1j*desired_angles) - z\n",
    "        # select smallest error\n",
    "        e = errors[numpy.argmin(numpy.abs(errors))]\n",
    "        \n",
    "        # dw = e * x.T / (x.x.T)\n",
    "        dw = (e * numpy.conj(inputs.T)) / (self.inputs + 1)\n",
    "        #print(\"dw = \", dw)\n",
    "        self.w += dw\n",
    "        #print(\"new self.w = \", self.w )\n",
    "        #print(\"test new self.w with query = \", self.query(inputs.T))\n",
    "        #print(\"--\")\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 222,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "w =  [[ 0.38574064+1.66513146j  1.22031900+2.16144084j  1.21310153+0.176633j\n",
      "  -1.12485307-0.25812644j -1.27266833-0.13015915j]]\n",
      "categories =  3\n",
      "periodicity =  3\n"
     ]
    }
   ],
   "source": [
    "# create instance of neural network\n",
    "number_of_inputs = 4\n",
    "categories = 3\n",
    "periods = 3\n",
    "\n",
    "n = neuralNetwork(number_of_inputs, categories, periods)\n",
    "n.status()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 223,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Type</th>\n",
       "      <th>PW</th>\n",
       "      <th>PL</th>\n",
       "      <th>SW</th>\n",
       "      <th>SL</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>72</th>\n",
       "      <td>0</td>\n",
       "      <td>0.04</td>\n",
       "      <td>0.16</td>\n",
       "      <td>0.34</td>\n",
       "      <td>0.50</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>88</th>\n",
       "      <td>0</td>\n",
       "      <td>0.02</td>\n",
       "      <td>0.15</td>\n",
       "      <td>0.37</td>\n",
       "      <td>0.54</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>98</th>\n",
       "      <td>0</td>\n",
       "      <td>0.02</td>\n",
       "      <td>0.15</td>\n",
       "      <td>0.35</td>\n",
       "      <td>0.52</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>0.02</td>\n",
       "      <td>0.14</td>\n",
       "      <td>0.33</td>\n",
       "      <td>0.50</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>0.24</td>\n",
       "      <td>0.56</td>\n",
       "      <td>0.31</td>\n",
       "      <td>0.67</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    Type    PW    PL    SW    SL\n",
       "72     0  0.04  0.16  0.34  0.50\n",
       "88     0  0.02  0.15  0.37  0.54\n",
       "98     0  0.02  0.15  0.35  0.52\n",
       "0      0  0.02  0.14  0.33  0.50\n",
       "1      1  0.24  0.56  0.31  0.67"
      ]
     },
     "execution_count": 223,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# load the iris training data CSV file into a list\n",
    "df = pandas.read_csv('iris_dataset/iris.csv')\n",
    "\n",
    "# scale the lengths\n",
    "df[['PW', 'PL', 'SW', 'SL']] = df[['PW', 'PL', 'SW', 'SL']].astype(numpy.float64) * 0.01\n",
    "\n",
    "# shuffle and split dataframe into train and test sets, split 3/4 and 1/4\n",
    "iris_train, iris_test = sklearn.cross_validation.train_test_split(df, train_size=0.75)\n",
    "iris_test.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 224,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# train neural network\n",
    "\n",
    "epochs = 2\n",
    "\n",
    "for e in range(epochs):\n",
    "    # go through all records in the training data set\n",
    "    for idx, data in iris_train.iterrows():\n",
    "        data_list = data.tolist()\n",
    "        species = data_list[0]\n",
    "        lengths = data_list[1:]\n",
    "        n.train(lengths, species)\n",
    "        pass\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 225,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "w =  [[ 0.94712904+1.0675523j   2.43183469+0.84406376j  1.07463099+0.46028561j\n",
      "  -0.61066543-0.54791292j -1.05720782+0.55283372j]]\n",
      "categories =  3\n",
      "periodicity =  3\n"
     ]
    }
   ],
   "source": [
    "n.status()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 226,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "0 0\n",
      "1 1\n",
      "2 2\n",
      "2 2\n",
      "1 1\n",
      "0 0\n",
      "0 0\n",
      "2 1\n",
      "1 1\n",
      "2 2\n",
      "2 2\n",
      "1 1\n",
      "1 1\n",
      "2 2\n",
      "2 2\n",
      "0 0\n",
      "1 1\n",
      "0 0\n",
      "2 2\n",
      "2 2\n",
      "1 1\n",
      "2 1\n",
      "2 2\n",
      "2 2\n",
      "0 0\n",
      "1 1\n",
      "1 1\n",
      "2 2\n",
      "1 1\n",
      "0 0\n",
      "0 0\n",
      "1 1\n",
      "2 2\n",
      "1 1\n",
      "0 0\n"
     ]
    }
   ],
   "source": [
    "# query after training\n",
    "\n",
    "scorecard = []\n",
    "\n",
    "for idx, data in iris_test.iterrows():\n",
    "    data_list = data.tolist()\n",
    "    correct_species = int(data_list[0])\n",
    "    lengths = data_list[1:]\n",
    "    answer = n.query(lengths)\n",
    "    print(correct_species, answer)\n",
    "    if (answer == correct_species):\n",
    "        # network's answer matches correct answer, add 1 to scorecard\n",
    "        scorecard.append(1)\n",
    "    else:\n",
    "        # network's answer doesn't match correct answer, add 0 to scorecard\n",
    "        scorecard.append(0)\n",
    "        pass\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 227,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "performance =  0.947368421053\n"
     ]
    }
   ],
   "source": [
    "# calculate the performance score, the fraction of correct answers\n",
    "scorecard_array = numpy.asarray(scorecard)\n",
    "print (\"performance = \", scorecard_array.sum() / scorecard_array.size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
